package org.framework.config;

import org.framework.annotation.RabbitSettingProducer;
import org.framework.annotation.properties.ArgumentSetting;
import org.framework.constant.MqQueueSetting;
import org.framework.constant.MqSetting;
import org.framework.factory.MessageBindingFactoryBean;
import org.framework.factory.MessageExchangeFactoryBean;
import org.framework.factory.MessageProducerFactoryBean;
import org.framework.factory.MessageQueueFactoryBean;
import org.framework.util.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.BeanExpressionContext;
import org.springframework.beans.factory.config.BeanExpressionResolver;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.context.annotation.ScannedGenericBeanDefinition;
import org.springframework.context.expression.StandardBeanExpressionResolver;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.StringUtils;

import java.lang.reflect.Array;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
 * @ClassName ClassPathMessageQueueScanner
 * @Description 扫描器
 * @Author RayLiang
 * @Date 2022/12/12 15:18
 * @Version 1.0
 **/
public class ClassPathMessageQueueScanner extends ClassPathBeanDefinitionScanner {

    private final Logger log = LoggerFactory.getLogger(this.getClass());

    private BeanDefinitionRegistry registry;

    private BeanFactory beanFactory;

    private BeanExpressionResolver resolver = new StandardBeanExpressionResolver();

    private BeanExpressionContext expressionContext;

    private Environment environment;

    private ClassLoader beanClassLoader;

    private Map<String,Pair<String,String>> dlxMap = new HashMap<>();

    private int increment;


    public ClassPathMessageQueueScanner(BeanDefinitionRegistry registry, BeanFactory beanFactory, Environment environment, BeanExpressionResolver resolver, BeanExpressionContext expressionContext, ClassLoader beanClassLoader) {
        super(registry, false);
        this.registry = registry;
        this.beanFactory = beanFactory;
        this.environment = environment;
        this.resolver = resolver;
        this.expressionContext = expressionContext;
        this.beanClassLoader = beanClassLoader;
    }


    @Override
    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
        return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
    }

    @Override
    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
        Map<String, BeanDefinition> BeanDefinitionNameMap = new HashMap<>(4);
        Set<BeanDefinitionHolder> beanDefinitionHolders = super.doScan(basePackages);
        for (BeanDefinitionHolder definitionHolder : beanDefinitionHolders) {
            ScannedGenericBeanDefinition beanDefinition = (ScannedGenericBeanDefinition) definitionHolder.getBeanDefinition();
            AnnotationMetadata metadata = beanDefinition.getMetadata();
            Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(RabbitSettingProducer.class.getName());
            AnnotationAttributes binding = (AnnotationAttributes) annotationAttributes.get("bindings");
            if (Objects.nonNull(binding)) {
                //queue
                AnnotationAttributes value = binding.getAnnotation("value");
                if(validateQueue(value,BeanDefinitionNameMap)){
                    BeanDefinition bdQueue = bulidQueueBeanDefinition(value);
                    BeanDefinitionNameMap.put(resolveExpression(value.get("value").toString()).toString(), bdQueue);
                }

                //exchange
                AnnotationAttributes exchange = binding.getAnnotation("exchange");
                if(validateExchange(exchange,BeanDefinitionNameMap)){
                    BeanDefinition bdExchange = bulidExchangeBeanDefinition(exchange);
                    BeanDefinitionNameMap.put(resolveExpression(exchange.get("value").toString()).toString(), bdExchange);
                }


                //binding
                String key = binding.getString("key");

                BeanDefinition bdBinding = bulidBindingBeanDefinition(resolveExpression(value.get("value").toString()).toString(), resolveExpression(exchange.get("value").toString()).toString(), key);
                String beanName = "binding." + resolveExpression(exchange.get("value").toString()).toString() + "." + resolveExpression(value.get("value").toString()).toString();
                BeanDefinitionNameMap.put(beanName, bdBinding);
            }

            //registry callback beandefinition
            Class callbackClass = (Class) annotationAttributes.get("callback");
            //is Abstract?
            if(!Modifier.isAbstract(callbackClass.getModifiers())){
                AbstractBeanDefinition bd = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
                bd.setBeanClassName(callbackClass.getName());
                bd.setBeanClass(callbackClass);
                bd.setPrimary(true);
                bd.setScope("singleton");
                registry.registerBeanDefinition(lowerFirst(callbackClass.getSimpleName()),bd);
            }

            String beanClassName = beanDefinition.getBeanClassName();
            beanDefinition.setBeanClass(MessageProducerFactoryBean.class);
            ConstructorArgumentValues constructorArgumentValues = beanDefinition.getConstructorArgumentValues();
            ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(beanClassName);
            constructorArgumentValues.addIndexedArgumentValue(0, valueHolder);
            String callbackFlag = environment.getProperty(MqSetting.callBackEnableSetting, "true");
            ConstructorArgumentValues.ValueHolder valueHolder1 = new ConstructorArgumentValues.ValueHolder(Boolean.valueOf(callbackFlag));
            constructorArgumentValues.addIndexedArgumentValue(1, valueHolder1);
            //beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
            beanDefinition.setPrimary(true);
        }


        for (Map.Entry<String, BeanDefinition> map : BeanDefinitionNameMap.entrySet()) {
            String beanName = map.getKey();
            BeanDefinition bd = map.getValue();
            registry.registerBeanDefinition(beanName, bd);
        }

        //handle dlxMap
        for(Map.Entry<String,Pair<String,String>> r : dlxMap.entrySet()){
            String exchangeName = r.getKey();
            String routeKey = r.getValue().getFirst();
            String queueName = r.getValue().getSecond();
            DirectExchange normalExchange = new DirectExchange(exchangeName, true, false);
            ((ConfigurableBeanFactory)this.beanFactory).registerSingleton(exchangeName + ++this.increment, normalExchange);
            Queue deadQueue = new Queue(queueName, true, false, false);
            ((ConfigurableBeanFactory)this.beanFactory).registerSingleton(queueName + ++this.increment, deadQueue);

            Binding dealBinding = BindingBuilder.bind(deadQueue).to(normalExchange).with(routeKey);
            ((ConfigurableBeanFactory)this.beanFactory).registerSingleton(exchangeName + "." + queueName + ++this.increment, dealBinding);

        }
        dlxMap.clear();
        this.dlxMap = null;

        return beanDefinitionHolders;
    }


    private BeanDefinition bulidQueueBeanDefinition(AnnotationAttributes value) {
        AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
        ConstructorArgumentValues constructorArgumentValues = beanDefinition.getConstructorArgumentValues();
        String v = resolveExpression(value.get("value").toString()).toString();
        if (!StringUtils.isEmpty(v)) {
            ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(v);
            constructorArgumentValues.addIndexedArgumentValue(0, valueHolder);
        }
        if (!StringUtils.isEmpty(value.get("durable"))) {
            ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(Boolean.valueOf(value.get("durable").toString()));
            constructorArgumentValues.addIndexedArgumentValue(1, valueHolder);
        } else {
            constructorArgumentValues.addIndexedArgumentValue(1, true);
        }
        if (!StringUtils.isEmpty(value.get("exclusive"))) {
            ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(Boolean.valueOf(value.get("exclusive").toString()));
            constructorArgumentValues.addIndexedArgumentValue(2, valueHolder);
        } else {
            constructorArgumentValues.addIndexedArgumentValue(2, false);
        }
        if (!StringUtils.isEmpty(value.get("autoDelete"))) {
            ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(Boolean.valueOf(value.get("autoDelete").toString()));
            constructorArgumentValues.addIndexedArgumentValue(3, valueHolder);
        } else {
            constructorArgumentValues.addIndexedArgumentValue(3, false);
        }

        AnnotationAttributes[] argumentsAA = (AnnotationAttributes[]) value.get("arguments");
        if (argumentsAA != null && argumentsAA.length > 0) {
            ArgumentSetting[] argumentSettings = (ArgumentSetting[]) Array.newInstance(ArgumentSetting.class, argumentsAA.length);
            String xdle = "";
            String xdlrk = "";
            String xdlbq = "";
           // boolean xdlbqFlag = false;
            int index = 0;
            for (int i = 0; i < argumentsAA.length; i++) {
                String name = argumentsAA[i].getString("name");
                String vl = argumentsAA[i].getString("value");
                String type = argumentsAA[i].getString("type");
                if(name.equals(MqQueueSetting.X_DEAD_LETTER_EXCHANGE)){
                    xdle = vl;
                }
                if(name.equals(MqQueueSetting.X_DEAD_LETTER_ROUTING_KEY)){
                    xdlrk = vl;
                }
                if(name.equals(MqQueueSetting.X_DEAD_LETTER_BINDING_QUEUE)){
                    xdlbq = vl;
                   // xdlbqFlag = true;
                    continue;//自定义arguemnt ,所以去除，不传入到队列中
                }

                ArgumentSetting argumentSetting = new ArgumentSetting(name, vl, type);
                argumentSettings[index] = argumentSetting;
                index++;

            }
//            ArgumentSetting[] argumentSettings1 = null;
//            if(xdlbqFlag){
//                argumentSettings1 = (ArgumentSetting[]) Array.newInstance(ArgumentSetting.class, argumentsAA.length-1);
//                for (int i = 0; i < argumentSettings1.length; i++) {
//                    argumentSettings1[i] = argumentSettings[i];
//                }
//            }

            if(!StringUtils.isEmpty(xdle)){
                dlxMap.put(xdle,Pair.of(xdlrk,xdlbq));
            }

            ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(argumentSettings);
            constructorArgumentValues.addIndexedArgumentValue(4, valueHolder);
        } else {
            constructorArgumentValues.addIndexedArgumentValue(4, new ArgumentSetting[0]);
        }
        constructorArgumentValues.addIndexedArgumentValue(5, this.beanFactory);//beanFactory
        constructorArgumentValues.addIndexedArgumentValue(6, this.resolver);//resolver
        constructorArgumentValues.addIndexedArgumentValue(7, this.expressionContext);//expressionContext
        constructorArgumentValues.addIndexedArgumentValue(8, this.beanClassLoader);//beanClassLoader
        ConstructorArgumentValues.ValueHolder orderHolder = new ConstructorArgumentValues.ValueHolder(Integer.valueOf(value.get("order").toString()));
        constructorArgumentValues.addIndexedArgumentValue(9,orderHolder);
        //beanDefinition.setFactoryBeanName(MessageQueueFactoryBean.class.getName());
        beanDefinition.setBeanClassName(MessageQueueFactoryBean.class.getName());
        beanDefinition.setBeanClass(MessageQueueFactoryBean.class);
        beanDefinition.setPrimary(true);
        beanDefinition.setScope("singleton");
        return beanDefinition;
    }


    private BeanDefinition bulidExchangeBeanDefinition(AnnotationAttributes value) {
        AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
        ConstructorArgumentValues constructorArgumentValues = beanDefinition.getConstructorArgumentValues();
        String v = resolveExpression(value.get("value").toString()).toString();
        if (!StringUtils.isEmpty(v)) {
            ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(v);
            constructorArgumentValues.addIndexedArgumentValue(0, valueHolder);
        }
        if (!StringUtils.isEmpty(value.get("type"))) {
            ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value.get("type").toString());
            constructorArgumentValues.addIndexedArgumentValue(1, valueHolder);
        }
        if (!StringUtils.isEmpty(value.get("durable"))) {
            ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(Boolean.valueOf(value.get("durable").toString()));
            constructorArgumentValues.addIndexedArgumentValue(2, valueHolder);
        }
        if (!StringUtils.isEmpty(value.get("autoDelete"))) {
            ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(Boolean.valueOf(value.get("autoDelete").toString()));
            constructorArgumentValues.addIndexedArgumentValue(3, valueHolder);
        }
        ConstructorArgumentValues.ValueHolder orderHolder = new ConstructorArgumentValues.ValueHolder(Integer.valueOf(value.get("order").toString()));
        constructorArgumentValues.addIndexedArgumentValue(4,orderHolder);
        beanDefinition.setBeanClass(MessageExchangeFactoryBean.class);
        beanDefinition.setBeanClassName(MessageExchangeFactoryBean.class.getName());
        beanDefinition.setPrimary(true);
        beanDefinition.setScope("singleton");
        return beanDefinition;
    }

    private BeanDefinition bulidBindingBeanDefinition(String queueName, String exchangeName, String key) {
        AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
        ConstructorArgumentValues constructorArgumentValues = beanDefinition.getConstructorArgumentValues();

        ConstructorArgumentValues.ValueHolder queueValueHolder = new ConstructorArgumentValues.ValueHolder(queueName);
        constructorArgumentValues.addIndexedArgumentValue(0, queueValueHolder);

        ConstructorArgumentValues.ValueHolder exchangeNameValueHolder = new ConstructorArgumentValues.ValueHolder(exchangeName);
        constructorArgumentValues.addIndexedArgumentValue(1, exchangeNameValueHolder);

            ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(key);
            constructorArgumentValues.addIndexedArgumentValue(2, valueHolder);
        beanDefinition.setBeanClass(MessageBindingFactoryBean.class);
        beanDefinition.setBeanClassName(MessageBindingFactoryBean.class.getName());
        beanDefinition.setPrimary(true);
        beanDefinition.setScope("singleton");
        return beanDefinition;
    }

    private Object resolveExpression(String value) {
        String resolvedValue = resolve(value);

        return this.resolver.evaluate(resolvedValue, this.expressionContext);
    }

    private String resolve(String value) {
        if (this.beanFactory != null && this.beanFactory instanceof ConfigurableBeanFactory) {
            return ((ConfigurableBeanFactory) this.beanFactory).resolveEmbeddedValue(value);
        }
        return value;
    }

    private boolean validateExchange(AnnotationAttributes exchange,Map<String, BeanDefinition> BeanDefinitionNameMap){
        String exchangeName = resolveExpression(exchange.get("value").toString()).toString();
        String exchangeType = exchange.get("type").toString();
        if(BeanDefinitionNameMap.containsKey(exchangeName)){
            BeanDefinition beanDefinition = BeanDefinitionNameMap.get(exchangeName);
            ConstructorArgumentValues constructorArgumentValues = beanDefinition.getConstructorArgumentValues();
            ConstructorArgumentValues.ValueHolder orderHolder = constructorArgumentValues.getIndexedArgumentValue(4, int.class);
            ConstructorArgumentValues.ValueHolder typeHolder = constructorArgumentValues.getIndexedArgumentValue(1, String.class);
            int oldOrder = Integer.valueOf(orderHolder.getValue().toString()).intValue();
            int newOrder = Integer.valueOf(exchange.get("order").toString()).intValue();
            String oldType = typeHolder.getValue().toString();
            if(!exchangeType.equals(oldType)){
                log.warn("duplicate exchange,but have different type,please check");
                return false;
            }
            if(oldOrder == newOrder){
                log.warn("duplicate exchange name have same priority level,please check");
                return false;
            }else if(newOrder > oldOrder){
                return true;
            }
        }
        return true;
    }

    private boolean validateQueue(AnnotationAttributes queue,Map<String, BeanDefinition> BeanDefinitionNameMap){
        String queueName = resolveExpression(queue.get("value").toString()).toString();
        if(BeanDefinitionNameMap.containsKey(queueName)){
            BeanDefinition beanDefinition = BeanDefinitionNameMap.get(queueName);
            ConstructorArgumentValues constructorArgumentValues = beanDefinition.getConstructorArgumentValues();
            ConstructorArgumentValues.ValueHolder orderHolder = constructorArgumentValues.getIndexedArgumentValue(9, int.class);
            int oldOrder = Integer.valueOf(orderHolder.getValue().toString()).intValue();
            int newOrder = Integer.valueOf(queue.get("order").toString()).intValue();
            if(oldOrder == newOrder){
                log.warn("duplicate queue name have same priority level,please check");
                return false;
            }else if(newOrder > oldOrder){
                return true;
            }
        }
        return true;
    }

    private static String lowerFirst(String str) {
        char[] cs=str.toCharArray();
        cs[0]+=32;
        return String.valueOf(cs);
    }

}
